package controller;

import controller.commands.AddBoxNodeCommand;
import controller.commands.AddConnectionCommand;
import controller.commands.BasicCommand;
import javax.swing.event.ChangeEvent;
import view.GUI.StatusBar;
import controller.commands.CloneCommand;
import controller.commands.ColorCommand;
import controller.commands.DeBoxCommand;
import controller.commands.DeleteCommand;
import controller.commands.EditNodeCommand;
import controller.commands.MacroCommand;
import controller.commands.RunCommand;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.JToolBar;
import javax.swing.event.ChangeListener;
import javax.swing.text.DefaultCaret;
import model.codebehind.CodeModel;
import model.graphic.GraphicModel;
import model.graphic.locations.AbsoluteLocation;
import model.graphic.objects.GBoxNode;
import model.graphic.objects.GNode;
import model.graphic.objects.Graphical;
import view.GUI.LabelReader;
import view.GUI.TabTitle;
import view.GUI.sidepanels.CustomArgument;
import view.GUI.sidepanels.ObserverTabbedPane;
import view.GUI.sidepanels.PropertiesPanel;
import view.GUI.sidepanels.Palette;
import view.GUI.sidepanels.RunPanel;
import view.GUI.WaterMarkTextArea;
import view.GUI.actions.AddBoxAction;
import view.GUI.actions.ClearHistoryAction;
import view.GUI.actions.DebugAction;
import view.GUI.actions.DebugredAction;
import view.GUI.actions.DeleteAction;
import view.GUI.actions.OneOnOneTranslationAction;
import view.GUI.actions.RedoAction;
import view.GUI.actions.RunAction;
import view.GUI.actions.UndoAction;
import view.GUI.graphic.GraphicView;
import view.GUI.sidepanels.InventoryPanel;

/**
 *
 * @author Jelle, modified by Ben
 */
public class Controller implements IListener {

    private GBoxNode rootNode;
    private GraphicModel gmodel;
    private CodeModel cmodel;
    private GraphicView gview;
    private JTextArea debug;
    private JTextArea output;
    private StatusBar statusbar;
    private ObserverTabbedPane tabRight;
    private PropertiesPanel propertiesPanel;
    private Palette palette;
    private Stack<State> history;
    private int pointer;
    private ArrayList<Class> listHistoryClasses = new ArrayList<Class>();
    private JToolBar toolbar;
    private JButton btnClear,  btnUndo,  btnRedo,  btnDelete,  btnBox, btnTranslation;
    private JButton btnClearHistory;
    private JButton btnRun;
    private RunPanel runpanel;
    private JButton btnDebug;
    private JButton btnDebugred;
    private JButton btnPause;
    private JButton btnStop;
    private StdOut stdOut;
    private JSlider sliderSpeed;
    private InventoryPanel inventoryPanel;
    private JPanel drawPanel;
    private JTabbedPane viewtabs;

    /** Constructor
     * @param gmodel
     * @param cmodel
     * @param view
     */
    public Controller() {
        this.rootNode = new GBoxNode("ROOT",new AbsoluteLocation(0,0), GraphicView.SIZE.width,GraphicView.SIZE.height);
        this.gmodel = rootNode.getGraphicModel();
        this.cmodel = new CodeModel();
        try {
            this.output = new WaterMarkTextArea(new File("icons\\connect.jpg"));
            this.output.setEditable(false);
            this.output.setWrapStyleWord(true);
            this.output.setLineWrap(true);
            this.output.append(LabelReader.getInstance().getString("PanelProvideOutputInfo"));
            DefaultCaret caret = (DefaultCaret) output.getCaret();
            caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);
            this.debug = new WaterMarkTextArea(new File("icons\\connectdebug.jpg"));
            this.debug.append(LabelReader.getInstance().getString("PanelProvideDebugInfo"));
            this.debug.setEditable(false);
            this.debug.setLineWrap(true);
            this.debug.setWrapStyleWord(true);
            DefaultCaret caret3 = (DefaultCaret) debug.getCaret();
            caret3.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);
        } catch (IOException ex) {
            Logger.getLogger(Controller.class.getName()).log(Level.SEVERE, null, ex);
        }
        this.gview = new GraphicView(gmodel, cmodel);
        this.stdOut = StdOut.getInstance();
        StdOut.getInstance().setPS(getOutputStream());
        this.statusbar = new StatusBar(gmodel);
        this.propertiesPanel = new PropertiesPanel();
        this.runpanel = new RunPanel(gview, gmodel, debug);
        this.tabRight = new ObserverTabbedPane(runpanel);
        this.palette = new Palette();
        this.toolbar = new JToolBar();
        this.toolbar.setFloatable(false);
        this.inventoryPanel = new InventoryPanel(gmodel,gview);
        this.drawPanel = new JPanel();
        this.drawPanel.setBackground(Color.WHITE);
        StdDraw.getInstance().setPanel(drawPanel);

        setArray();
        setToolbar();
        debug.setRows(5);
        output.setRows(5);

        JScrollPane scr1 = new JScrollPane(propertiesPanel, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        scr1.setBorder(null);
        JScrollPane scr2 = new JScrollPane(palette, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        scr2.setBorder(null);
        JScrollPane scr3 = new JScrollPane(runpanel, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        JScrollPane scr4 = new JScrollPane(inventoryPanel, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        tabRight.addTab(LabelReader.getInstance().getString("Properties"), scr1);
        tabRight.addTab(LabelReader.getInstance().getString("Palette"), scr2);
        tabRight.addTab(LabelReader.getInstance().getString("Run"), scr3);
        tabRight.addTab(LabelReader.getInstance().getString("Inventory"), scr4);
        tabRight.setSelectedIndex(1);

        gview.addObserver(this);
        gmodel.addObserver(propertiesPanel);
        gmodel.addObserver(statusbar);

        pointer = 0;
        this.history = new Stack<State>();
        State os = new State(gmodel, cmodel);
        history.push(os);
        updateNavigationIcons();

        viewtabs = new JTabbedPane();
        viewtabs.addChangeListener(new ChangeListener(){
            public void stateChanged(ChangeEvent ce) {
                ((GraphicView)((JScrollPane)viewtabs.getSelectedComponent()).getViewport().getView()).addAllNodesFromModel();
            }
        });

    }

    public GraphicView getSelectedGraphicView(){
        return ((GraphicView)((JScrollPane)viewtabs.getSelectedComponent()).getViewport().getView());
    }

    public void clearhistory() {
        pointer = 0;
        this.history = new Stack<State>();
        State os = new State(gmodel, cmodel);
        history.push(os);
        updateNavigationIcons();
    }

    public void undo() {
        if (pointer > 0) {
            State st = history.get(pointer - 1);
            pointer--;
            cmodel.setLink(st.getClink());
            cmodel.setObjects(st.getCobjects());
            gmodel.setLink(st.getGlink());
            gmodel.setObjects(st.getGobjects());
            gmodel.setSelection(st.getGselection());
            gmodel.setModelChanged();
            gmodel.notifyObservers(null);

        }
        updateNavigationIcons();
        gview.repaint();
    }

    public void redo() {
        if (pointer >= 0 && pointer < history.size() - 1) {
            State st = history.get(pointer + 1);
            pointer++;
            cmodel.setLink(st.getClink());
            cmodel.setObjects(st.getCobjects());
            gmodel.setLink(st.getGlink());
            gmodel.setObjects(st.getGobjects());
            gmodel.setSelection(st.getGselection());
            gmodel.setModelChanged();
            gmodel.notifyObservers(null);
        }
        updateNavigationIcons();
        gview.repaint();
    }

    private OutputStream textArea2OutputStream(final JTextArea t) {
        return new OutputStream() {

            JTextArea ta = t;

            public void write(int b) {
                byte[] bs = new byte[1];
                bs[0] = (byte) b;
                ta.append(new String(bs));
            }
        };
    }

    public PrintStream getOutputStream() {
        return new PrintStream(textArea2OutputStream(output));
    }

    private void setArray() {
        listHistoryClasses.add(AddBoxNodeCommand.class);
        listHistoryClasses.add(AddConnectionCommand.class);
        listHistoryClasses.add(CloneCommand.class);
        listHistoryClasses.add(ColorCommand.class);
        //listHistoryClasses.add(CollapseCommand.class);
        listHistoryClasses.add(DeBoxCommand.class);
        listHistoryClasses.add(DeleteCommand.class);
        listHistoryClasses.add(EditNodeCommand.class);
        listHistoryClasses.add(MacroCommand.class);
    }

    private void setToolbar() {
        btnClear = new JButton(new DeleteAction(true, getGview()));
        btnClear.setIcon(new ImageIcon("icons\\clear.png"));
        btnClear.setToolTipText(LabelReader.getInstance().getString("ClearPanel"));
//        btnClear.setText("Clear panel");
//        btnClear.setHorizontalTextPosition(SwingConstants.CENTER);
//        btnClear.setVerticalTextPosition(SwingConstants.BOTTOM);
        btnClear.setEnabled(false);
        toolbar.add(btnClear);

        btnDelete = new JButton();
        btnDelete.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                ArrayList<Graphical> overloop = new ArrayList<Graphical>();
                overloop = (ArrayList<Graphical>) gview.getAllNodes().clone();
                for (Graphical g : overloop) {
                    if (g.isSelected()) {
                        gview.notifyObservers(new DeleteCommand(g));
                    }
                }
                gview.notifyObservers(new CustomArgument("tabRight", 1));
                gview.repaint();
            }
        });
        btnDelete.setIcon(new ImageIcon("icons\\delete.png"));
        btnDelete.setToolTipText(LabelReader.getInstance().getString("DeleteSelection"));
//        btnDelete.setText("Delete selection");
//        btnDelete.setHorizontalTextPosition(SwingConstants.CENTER);
//        btnDelete.setVerticalTextPosition(SwingConstants.BOTTOM);
        btnDelete.setEnabled(false);
        toolbar.add(btnDelete);

        toolbar.addSeparator();

        btnBox = new JButton(new AddBoxAction(getGview(), getGmodel(), getPalette()));
        btnBox.setIcon(new ImageIcon("icons\\box.png"));
        btnBox.setToolTipText(LabelReader.getInstance().getString("MakeBoxnode"));
//        btnBox.setText("Make boxnode");
//        btnBox.setHorizontalTextPosition(SwingConstants.CENTER);
//        btnBox.setVerticalTextPosition(SwingConstants.BOTTOM);
        btnBox.setEnabled(false);
        toolbar.add(btnBox);

        btnTranslation = new JButton(new OneOnOneTranslationAction(getGview(), getGmodel()));
        btnTranslation.setIcon(new ImageIcon("icons\\translate.png"));
        btnTranslation.setToolTipText(LabelReader.getInstance().getString("OneOnOneTranslation"));
        btnTranslation.setEnabled(true);
        toolbar.add(btnTranslation);

        toolbar.addSeparator();

        btnUndo = new JButton(new UndoAction(this));
        btnUndo.setIcon(new ImageIcon("icons\\undo.png"));
        btnUndo.setToolTipText(LabelReader.getInstance().getString("UndoAction"));
//        btnUndo.setText("Undo action");
//        btnUndo.setHorizontalTextPosition(SwingConstants.CENTER);
//        btnUndo.setVerticalTextPosition(SwingConstants.BOTTOM);
        btnUndo.setEnabled(false);
        btnUndo.setMnemonic(KeyEvent.VK_Z);
        toolbar.add(btnUndo);

        btnClearHistory = new JButton(new ClearHistoryAction(this));
        btnClearHistory.setIcon(new ImageIcon("icons\\clearhistory.png"));
        btnClearHistory.setToolTipText(LabelReader.getInstance().getString("ClearHistory"));
//        btnClearHistory.setText("Clear history");
//        btnClearHistory.setHorizontalTextPosition(SwingConstants.CENTER);
//        btnClearHistory.setVerticalTextPosition(SwingConstants.BOTTOM);
        btnClearHistory.setEnabled(false);


        toolbar.add(btnClearHistory);

        btnRedo = new JButton(new RedoAction(this));
        btnRedo.setIcon(new ImageIcon("icons\\redo.png"));
        btnRedo.setToolTipText(LabelReader.getInstance().getString("RedoAction"));
//        btnRedo.setText("Redo action");
//        btnRedo.setHorizontalTextPosition(SwingConstants.CENTER);
//        btnRedo.setVerticalTextPosition(SwingConstants.BOTTOM);
        btnRedo.setEnabled(false);
        btnRedo.setMnemonic(KeyEvent.VK_Y);
        toolbar.add(btnRedo);

        toolbar.addSeparator();

        btnRun = new JButton(new RunAction(gview, gmodel));
        btnRun.setIcon(new ImageIcon("icons\\run.png"));
        btnRun.setToolTipText(LabelReader.getInstance().getString("RunProgram"));
//        btnRun.setText("Run program");
//        btnRun.setHorizontalTextPosition(SwingConstants.CENTER);
//        btnRun.setVerticalTextPosition(SwingConstants.BOTTOM);
        btnRun.setEnabled(true);
        btnRun.setMnemonic(KeyEvent.VK_E);
        toolbar.add(btnRun);

        btnDebug = new JButton(new DebugAction(gview, gmodel));
        btnDebug.setIcon(new ImageIcon("icons\\debug.png"));
        btnDebug.setToolTipText(LabelReader.getInstance().getString("StepByStepRun"));
        btnDebug.setEnabled(true);
        btnDebug.setMnemonic(KeyEvent.VK_O);
//        btnDebug.setText("Step by step run");
//        btnDebug.setHorizontalTextPosition(SwingConstants.CENTER);
//        btnDebug.setVerticalTextPosition(SwingConstants.BOTTOM);
        toolbar.add(btnDebug);

        btnDebugred = new JButton(new DebugredAction(gview, gmodel, debug));
        btnDebugred.setIcon(new ImageIcon("icons\\debugred.png"));
        btnDebugred.setToolTipText(LabelReader.getInstance().getString("SlowMotionRun"));
//        btnDebugred.setText("Slow motion run");
//        btnDebugred.setHorizontalTextPosition(SwingConstants.CENTER);
//        btnDebugred.setVerticalTextPosition(SwingConstants.BOTTOM);
        btnDebugred.setEnabled(true);
        btnDebugred.setMnemonic(KeyEvent.VK_R);
        toolbar.add(btnDebugred);

        toolbar.addSeparator();

        btnPause = new JButton();
        btnPause.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                if (!gview.isPaused()) {
                    gview.getExecuteThread().suspend();
                    gview.setPause(true);
                    btnPause.setIcon(new ImageIcon("icons\\debugred.png"));
                } else {
                    gview.getExecuteThread().resume();
                    gview.setPause(false);
                    btnPause.setIcon(new ImageIcon("icons\\pause.png"));
                }
            }
        });
        btnPause.setIcon(new ImageIcon("icons\\pause.png"));
        btnPause.setToolTipText(LabelReader.getInstance().getString("PauseDebug"));
//        btnPause.setText("Pause debug");
//        btnPause.setHorizontalTextPosition(SwingConstants.CENTER);
//        btnPause.setVerticalTextPosition(SwingConstants.BOTTOM);
        btnPause.setEnabled(false);
        btnPause.setMnemonic(KeyEvent.VK_P);
        toolbar.add(btnPause);

        btnStop = new JButton();
        btnStop.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                gview.getExecuteThread().stop();
                updateRunButtons(true);
                runpanel.updateRunButtons(true);
            }
        });
        btnStop.setIcon(new ImageIcon("icons\\stop.png"));
        btnStop.setToolTipText(LabelReader.getInstance().getString("StopDebug"));
//        btnStop.setText("Stop debug");
//        btnStop.setHorizontalTextPosition(SwingConstants.CENTER);
//        btnStop.setVerticalTextPosition(SwingConstants.BOTTOM);
        btnStop.setEnabled(false);
        btnStop.setMnemonic(KeyEvent.VK_S);
        toolbar.add(btnStop);

        sliderSpeed = new JSlider(JSlider.HORIZONTAL, 100, 500, 300);
        sliderSpeed.addChangeListener(new ChangeListener(){
            public void stateChanged(ChangeEvent e) {
                JSlider source = (JSlider)e.getSource();
                if (!source.getValueIsAdjusting()){
                    Constants.setDebugTime(source.getValue());
                }
            }

        });
        sliderSpeed.setMajorTickSpacing(100);
        sliderSpeed.setMinorTickSpacing(10);
        sliderSpeed.setPaintTicks(true);
        sliderSpeed.setToolTipText(LabelReader.getInstance().getString("AdjustDebugSpeed"));
        sliderSpeed.setPreferredSize(new Dimension(150,30));
        sliderSpeed.setMaximumSize(new Dimension(150,30));
        sliderSpeed.setMinimumSize(new Dimension(150,30));
        sliderSpeed.setEnabled(false);
        toolbar.add(sliderSpeed);


    }

    private void updateClearButton() {
        if (gmodel.getObjects().size() > 0) {
            btnClear.setEnabled(true);
        } else {
            btnClear.setEnabled(false);
        }
    }

    private void updateSelectionButtons() {
        btnDelete.setEnabled(false);
        btnBox.setEnabled(false);
        for (Graphical g : gmodel.getSelection()) {
            if (GNode.class.isAssignableFrom(g.getClass())) {
                btnDelete.setEnabled(true);
                btnBox.setEnabled(true);
            } else if (GBoxNode.class.isAssignableFrom(g.getClass())) {
                btnDelete.setEnabled(true);
            }
        }
    }

    private void updateNavigationIcons() {
        if (history.size() == 1) {
            btnUndo.setEnabled(false);
            btnRedo.setEnabled(false);
            btnClearHistory.setEnabled(false);
        } else if (pointer == 0) {
            btnUndo.setEnabled(false);
            btnRedo.setEnabled(true);
            btnClearHistory.setEnabled(true);
        } else if (pointer == history.size() - 1) {
            btnUndo.setEnabled(true);
            btnRedo.setEnabled(false);
            btnClearHistory.setEnabled(true);
        } else {
            btnUndo.setEnabled(true);
            btnRedo.setEnabled(true);
            btnClearHistory.setEnabled(true);
        }
    }

    private void updateRunButtons(boolean b) {
        btnRun.setEnabled(b);
        btnDebug.setEnabled(b);
        btnDebugred.setEnabled(b);
        btnPause.setEnabled(!b);
        btnStop.setEnabled(!b);
        sliderSpeed.setEnabled(!b);

    }

    public void update(BasicCommand arg) {
        updateSelectionButtons();
        updateClearButton();
        getSelectedGraphicView().requestFocus();
        if (CustomArgument.class.isAssignableFrom(arg.getClass())) {
            CustomArgument carg = (CustomArgument) arg;
            if (carg.getTarget().equals("tabRight")
                && (carg.getIntArgument() != tabRight.getSelectedIndex())) {
                    tabRight.setSelectedIndex(carg.getIntArgument());
            }
        } else if (arg != null) {
            try {
                //System.out.println("Controller is aangeroepen om een command uit te voeren met type:" + arg.getClass());
                arg.setGview(gview);
                arg.setCModel(cmodel);
                arg.setGModel(getSelectedGraphicView().getBoxnodeParent().getGraphicModel());//arg.setGModel(gmodel);
                arg.setSync(BidirectionalHashMap.getInstance());
                arg.setController(this);
                arg.execute();
                if (RunCommand.class.isAssignableFrom(arg.getClass())) {
                    this.updateRunButtons(((RunCommand) arg).getBool());
                    runpanel.updateRunButtons(((RunCommand) arg).getBool());
                }
                if (listHistoryClasses.contains(arg.getClass())) {
                    State os = new State(gmodel, cmodel);
                    pointer++;
                    history.push(os);
                    updateNavigationIcons();
                }
            } catch (Exception ex) {
                ex.printStackTrace();
                System.out.println(LabelReader.getInstance().getString("ControllerUpdateCodemodelEXCEPTION") + arg.getClass().getSimpleName() + ex.getMessage() + "-" + ex.getCause());
            }

        }
    }

    /**
     * @return the gmodel
     */
    public GraphicModel getGmodel() {
        return gmodel;
    }

    /**
     * @return the gview
     */
    public GraphicView getGview() {
        return gview;
    }

    /**
     * @return the debug
     */
    public JTextArea getDebug() {
        return debug;
    }

    /**
     * @return the output
     */
    public JTextArea getOutput() {
        return output;
    }

    /**
     * @return the drawing panel
     */
    public JPanel getDrawPanel(){
        return drawPanel;
    }


    /**
     * @return the cmodel
     */
    public CodeModel getCmodel() {
        return cmodel;
    }

    /**
     * @return the propertiesPanel
     */
    public PropertiesPanel getPropertiesPanel() {
        return propertiesPanel;
    }

    /**
     * @param propertiesPanel the propertiesPanel to set
     */
    public void setPropertiesPanel(PropertiesPanel propertiesPanel) {
        this.propertiesPanel = propertiesPanel;
    }

    /**
     * @return the statusbar
     */
    public StatusBar getStatusbar() {
        return statusbar;
    }

    /**
     * @return the tabRight
     */
    public JTabbedPane getTabRight() {
        return tabRight;
    }

    /**
     * @return the palette
     */
    public Palette getPalette() {
        return palette;
    }

    /**
     * @param cmodel the cmodel to set
     */
    public void setCmodel(CodeModel cmodel) {
        this.cmodel = cmodel;
    }

    /**
     * @return the toolbar
     */
    public JToolBar getToolbar() {
        return toolbar;
    }

    public JTabbedPane getViewtabs() {
        return viewtabs;
    }

    public void addViewTab(String name,JScrollPane scroll, boolean showClose){
       viewtabs.add(name,scroll);
       viewtabs.setSelectedIndex(viewtabs.getTabCount()-1);
       viewtabs.setTabComponentAt(viewtabs.getTabCount()-1, new TabTitle(name,viewtabs,showClose));
    }

    public void addViewTab(String name,GraphicView gv, boolean showClose){
       JScrollPane scroll = new JScrollPane(gv, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
       scroll.setSize(200, 200);
       addViewTab(name,scroll,showClose);
    }

}
